//--------------------------------------------------------------------------------------
// File: EmptyProject11.cpp
//
// Empty starting point for new Direct3D 9 and/or Direct3D 11 applications
//
// Copyright (c) Microsoft Corporation. All rights reserved.
//--------------------------------------------------------------------------------------
#include "DXUT.h"
#include "..\DXUT11\Optional\DXUTcamera.h"

#include <xnamath.h>
#include <vector>
#include <string>
#include <map>

#include "render.h"
#include "q3bsp.h"
#include "log.h"

Q3BSP* g_bsp = NULL;

inline float ToRadians(float degrees)
{
	return (XM_PI / 180.0f) * degrees;
}

ID3D11RasterizerState*	g_rasterizeStateSolid = NULL;
ID3D11RasterizerState*	g_rasterizeStateWireframe = NULL;

ID3D11VertexShader* g_vertexShader = NULL;
ID3D11PixelShader* g_diffuseShader = NULL;
ID3D11InputLayout* g_inputLayout = NULL;
ID3D11SamplerState*				 g_pSamplerLinear = NULL;
ID3D11Buffer* g_vertexBuffer = NULL;
ID3D11Buffer* g_constantBuffer = NULL;
XMMATRIX g_world;
XMMATRIX g_view;
XMMATRIX g_projection;
bool g_wireframe = true;

XMVECTOR g_camPos;
float g_camXDegrees = 0.0f, g_camYDegrees = 0.0f;
float g_camXRot = 0.0f, g_camYRot = 0.0f;
float g_camSpeed = 0.0f;
float g_camStrafeSpeed = 0.0f;
float g_camVerticalSpeed = 0.0f;

//--------------------------------------------------------------------------------------
// Helper for compiling shaders with D3DX11
//--------------------------------------------------------------------------------------
namespace {
// TODO only 1 copy of this would be sensible
HRESULT CompileShaderFromFile(WCHAR* szFileName, D3D_SHADER_MACRO* macros, LPCSTR szEntryPoint, LPCSTR szShaderModel, ID3DBlob** ppBlobOut)
{
    HRESULT hr = S_OK;

    DWORD dwShaderFlags = D3DCOMPILE_ENABLE_STRICTNESS;
#if defined(DEBUG) || defined(_DEBUG)
    // Set the D3DCOMPILE_DEBUG flag to embed debug information in the shaders.
    // Setting this flag improves the shader debugging experience, but still allows 
    // the shaders to be optimized and to run exactly the way they will run in 
    // the release configuration of this program.
    dwShaderFlags |= D3DCOMPILE_DEBUG;
#endif

    ID3DBlob* pErrorBlob;
    hr = D3DX11CompileFromFile(szFileName, macros, NULL, szEntryPoint, szShaderModel, 
        dwShaderFlags, 0, NULL, ppBlobOut, &pErrorBlob, NULL);
    if(FAILED(hr))
    {
        if(pErrorBlob != NULL)
		{
			MessageBoxA(NULL, (char*)pErrorBlob->GetBufferPointer(), "Compile Error", MB_OK | MB_SYSTEMMODAL); 
		}
       //     OutputDebugStringA((char*)pErrorBlob->GetBufferPointer());
        if(pErrorBlob) pErrorBlob->Release();
        return hr;
    }
    if(pErrorBlob) pErrorBlob->Release();

    return S_OK;
}
}

BlendFuncParams ParseBlendFuncParam(const std::string& param)
{
	struct BlendParams
	{
		std::string		str;
		BlendFuncParams	value;
	};

	const BlendParams params[] =
	{
		{ "GL_ONE", Blend_One },
		{ "GL_ZERO", Blend_Zero },
		{ "GL_DST_COLOR", Blend_DstColour },
		{ "GL_SRC_ALPHA", Blend_SrcAlpha },
		{ "GL_ONE_MINUS_SRC_ALPHA", Blend_OneMinusSrcAlpha },
		{ "", Blend_None },
	};

	const BlendParams* blendParams = &params[0];
	while (blendParams->str != "")
	{
		if (blendParams->str == param)
		{
			return blendParams->value;
		}

		blendParams++;
	}

	return Blend_None;
}

std::vector<std::string> Tokenise(const std::string& str)
{
	// include newlines in the delims so we drop the line endings
	const char* delims = " \t\n\r";
	char buffer[512];
	strcpy(buffer, str.c_str());

	std::vector<std::string> tokens;
	char* t = strtok(buffer, delims);
	while (t != NULL)
	{
		tokens.push_back(t);
		t = strtok(NULL, delims);
	}

	return tokens;
}

int ParseShader(const std::string& rootDir, char* data) 
{
	// TODO stop using strtok
	std::vector<std::string> lines;
	char* line = strtok(data, "\n");
	while (line != NULL)
	{
		lines.push_back(line);
		line = strtok(NULL, "\n");
	}

	const int STATE_SHADER_NAME = 0;
	const int STATE_PARAMS = 1;
	const int STATE_STAGE = 2;
	const int STATE_FINISHED_STAGE = 3;
	int state = STATE_SHADER_NAME;

	std::vector<Shader> shaders;
	Shader s;
	ShaderStage stage;

	for (auto lineIter = lines.begin(); lineIter != lines.end(); ++lineIter)
	{
		const std::string& line = *lineIter;

		std::vector<std::string> tokens = Tokenise(line);
		if (tokens.size() == 0 ||
			(tokens[0][0] == '/' && tokens[0][1] == '/'))
		{
			continue;
		}

		switch (state)
		{
		case STATE_SHADER_NAME:
		{
			if (s.name != "")
			{
				shaders.push_back(s);
				s = Shader();
			}

			s.name = tokens[0];
			state = STATE_PARAMS;
			break;
		}

		case STATE_PARAMS:
		{
			// TODO parse params
			if (tokens[0] == "{")
			{
				state = STATE_STAGE;
			}

			break;
		}

		case STATE_STAGE:
		{
			if (tokens[0] == "}")
			{
				s.stages.push_back(stage);
				state = STATE_FINISHED_STAGE;
			}
			else if (tokens[0] == "map")
			{
				if (tokens[1] == "$lightmap")
				{
					stage.texture = tokens[1];
				}
				else
				{
					stage.texture = rootDir + tokens[1];
				}
			}
			else if (tokens[0] == "blendFunc")
			{
				if (tokens.size() > 1)
				{
					stage.srcParam = ParseBlendFuncParam(tokens[1]);
				}

				if (tokens.size() > 2)
				{
					stage.dstParam = ParseBlendFuncParam(tokens[2]);
				}
			}

			break;
		}

		case STATE_FINISHED_STAGE:
		{
			if (tokens[0] == "{")
			{
				state = STATE_STAGE;
			}
			else if (tokens[0] == "}")
			{
				state = STATE_SHADER_NAME;
			}

			break;
		}

		}
	}

	int shadersCreated = 0;
	for (auto iter = shaders.begin(); iter != shaders.end(); ++iter)
	{
		if (!Render_CreateShader(*iter))
		{
			Log_Printf("Error: could not create render shader for shader '%s'\n", (*iter).name.c_str());
		}
		else
		{
			shadersCreated++;
		}
	}

	return shadersCreated;
}

void LoadShaders(const std::string& rootDir)
{
	std::vector<std::string> shaderFiles;
	{
		const std::string searchPath = rootDir + "scripts/*shader";
		WIN32_FIND_DATAA findData;
		HANDLE file = FindFirstFileA(searchPath.c_str(), &findData);
		while (file != INVALID_HANDLE_VALUE)
		{
			shaderFiles.push_back(rootDir + std::string("scripts/") + std::string(findData.cFileName));

			if (!FindNextFileA(file, &findData))
			{
				file = INVALID_HANDLE_VALUE;
			}
		}
	}

	std::map<std::string, char*> shaderData;
	for (auto iter = shaderFiles.begin(); iter != shaderFiles.end(); ++iter)
	{
		FILE* file = fopen((*iter).c_str(), "r");
		if (file == NULL)
		{
			Log_Printf("Error: couldn't open file '%s'\n", (*iter).c_str());
			continue;
		}

		fseek(file, 0, SEEK_END);
		const size_t filesize = ftell(file);
		rewind(file);

		char* filedata = new char[filesize];
		memset(filedata, 0, filesize * sizeof(char));
		fread(filedata, 1, filesize, file);

		shaderData[*iter] = filedata;
		fclose(file);
	}

	int shadersCreated = 0;
	for (auto iter = shaderData.begin(); iter != shaderData.end(); ++iter)
	{
		const std::string& shader = iter->first;
		char* data = iter->second;

		shadersCreated += ParseShader(rootDir, data);
		delete[] data;
	}

	Log_Printf("Created %d render shaders\n", shadersCreated);
}


//--------------------------------------------------------------------------------------
// Extern declarations 
//--------------------------------------------------------------------------------------
extern bool CALLBACK IsD3D9DeviceAcceptable(D3DCAPS9* pCaps, D3DFORMAT AdapterFormat, D3DFORMAT BackBufferFormat,
                                             bool bWindowed, void* pUserContext);
extern HRESULT CALLBACK OnD3D9CreateDevice(IDirect3DDevice9* pd3dDevice,
                                            const D3DSURFACE_DESC* pBackBufferSurfaceDesc, void* pUserContext);
extern HRESULT CALLBACK OnD3D9ResetDevice(IDirect3DDevice9* pd3dDevice, const D3DSURFACE_DESC* pBackBufferSurfaceDesc,
                                           void* pUserContext);
extern void CALLBACK OnD3D9FrameRender(IDirect3DDevice9* pd3dDevice, double fTime, float fElapsedTime,
                                        void* pUserContext);
extern void CALLBACK OnD3D9LostDevice(void* pUserContext);
extern void CALLBACK OnD3D9DestroyDevice(void* pUserContext);


//--------------------------------------------------------------------------------------
// Reject any D3D11 devices that aren't acceptable by returning false
//--------------------------------------------------------------------------------------
bool CALLBACK IsD3D11DeviceAcceptable(const CD3D11EnumAdapterInfo *AdapterInfo, UINT Output, const CD3D11EnumDeviceInfo *DeviceInfo,
                                       DXGI_FORMAT BackBufferFormat, bool bWindowed, void* pUserContext)
{
    return true;
}


//--------------------------------------------------------------------------------------
// Called right before creating a D3D9 or D3D11 device, allowing the app to modify the device settings as needed
//--------------------------------------------------------------------------------------
bool CALLBACK ModifyDeviceSettings(DXUTDeviceSettings* pDeviceSettings, void* pUserContext)
{
    return true;
}


//--------------------------------------------------------------------------------------
// Create any D3D11 resources that aren't dependant on the back buffer
//--------------------------------------------------------------------------------------
HRESULT CALLBACK OnD3D11CreateDevice(ID3D11Device* pd3dDevice, const DXGI_SURFACE_DESC* pBackBufferSurfaceDesc,
                                      void* pUserContext)
{
	HRESULT hr;


	ID3DBlob* vertexShaderBlob = NULL;
	hr = CompileShaderFromFile(L"Tutorial02.fx", NULL, "VS", "vs_4_0", &vertexShaderBlob);
	if (FAILED(hr))
	{
		MessageBox(NULL, L"Unable to compile vertex shader from file", L"Error", MB_OK);
		return hr;
	}

	hr = pd3dDevice->CreateVertexShader(vertexShaderBlob->GetBufferPointer(), vertexShaderBlob->GetBufferSize(),
		NULL, &g_vertexShader);
	if (FAILED(hr))
	{
		vertexShaderBlob->Release();
		return hr;
	}

	D3D11_INPUT_ELEMENT_DESC layout[] = 
	{
		{ "TEXCOORD", 0, DXGI_FORMAT_R32G32_FLOAT, 0, 0, D3D11_INPUT_PER_VERTEX_DATA, 0 },
		{ "TEXCOORD", 1, DXGI_FORMAT_R32G32_FLOAT, 0, 8, D3D11_INPUT_PER_VERTEX_DATA, 0 },
		{ "POSITION", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 8 + 8, D3D11_INPUT_PER_VERTEX_DATA, 0 },
	};
	UINT numElements = ARRAYSIZE(layout);

	hr = pd3dDevice->CreateInputLayout(layout, numElements, vertexShaderBlob->GetBufferPointer(),
		vertexShaderBlob->GetBufferSize(), &g_inputLayout);
	vertexShaderBlob->Release();
	if (FAILED(hr))
	{
		return hr;
	}

	{
		ID3DBlob* pixelShaderBlob = NULL;
		hr = CompileShaderFromFile(L"Tutorial02.fx", NULL, "PS_Diffuse", "ps_4_0", &pixelShaderBlob);
		if (FAILED(hr))
		{
			return hr;
		}
	
		hr = pd3dDevice->CreatePixelShader(pixelShaderBlob->GetBufferPointer(), pixelShaderBlob->GetBufferSize(),
			NULL, &g_diffuseShader);
		pixelShaderBlob->Release();
		if (FAILED(hr))
		{
			return hr;
		}
	}

	SimpleVertex* worldVerts = NULL;
	int* worldIndices = NULL;
	int numWorldVerts = 0, numWorldIndices = 0;
//	Q3BSP_GetVertexData(g_bsp, &g_worldVerts, &numWorldVerts);
//	Q3BSP_GetIndexData(g_bsp, &worldIndices, &numWorldIndices);
	Q3BSP_GetWorldSurfaceData(g_bsp, &worldVerts, &numWorldVerts, &worldIndices, &numWorldIndices);
	
	{
		D3D11_BUFFER_DESC bd;
		ZeroMemory(&bd, sizeof(bd));
		bd.Usage = D3D11_USAGE_IMMUTABLE;
		bd.ByteWidth = sizeof(SimpleVertex) * numWorldVerts;
		bd.BindFlags = D3D11_BIND_VERTEX_BUFFER;
		bd.CPUAccessFlags = 0;
	
		D3D11_SUBRESOURCE_DATA initData;
		ZeroMemory(&initData, sizeof(initData));
		initData.pSysMem = worldVerts;
		hr = pd3dDevice->CreateBuffer(&bd, &initData, &g_vertexBuffer);
		if (FAILED(hr))
		{
			return hr;
		}

		delete[] worldVerts;
		delete[] worldIndices;
	}

	D3D11_BUFFER_DESC bd;
	ZeroMemory(&bd, sizeof(bd));
	bd.Usage = D3D11_USAGE_DEFAULT;
	bd.ByteWidth = sizeof(ConstantBuffer);
	bd.BindFlags = D3D11_BIND_CONSTANT_BUFFER;
	bd.CPUAccessFlags = 0;
	hr = pd3dDevice->CreateBuffer(&bd, NULL, &g_constantBuffer);
	if (FAILED(hr))
	{
		return hr;
	}
	
	D3D11_RASTERIZER_DESC rasterDesc;
	ZeroMemory(&rasterDesc, sizeof(rasterDesc));
	rasterDesc.FillMode = D3D11_FILL_SOLID;
	rasterDesc.CullMode = D3D11_CULL_BACK;
	rasterDesc.DepthClipEnable = TRUE;
	hr = pd3dDevice->CreateRasterizerState(&rasterDesc, &g_rasterizeStateSolid);
	if (FAILED(hr))
	{
		return hr;
	}

	rasterDesc.FillMode = D3D11_FILL_WIREFRAME;
	hr = pd3dDevice->CreateRasterizerState(&rasterDesc, &g_rasterizeStateWireframe);
	if (FAILED(hr))
	{
		return hr;
	}

	// Create the sample state
	D3D11_SAMPLER_DESC sampDesc;
	ZeroMemory( &sampDesc, sizeof(sampDesc) );
	sampDesc.Filter = D3D11_FILTER_MIN_MAG_MIP_LINEAR;
	sampDesc.AddressU = D3D11_TEXTURE_ADDRESS_WRAP;
	sampDesc.AddressV = D3D11_TEXTURE_ADDRESS_WRAP;
	sampDesc.AddressW = D3D11_TEXTURE_ADDRESS_WRAP;
	sampDesc.ComparisonFunc = D3D11_COMPARISON_NEVER;
	sampDesc.MinLOD = 0;
	sampDesc.MaxLOD = D3D11_FLOAT32_MAX;
	hr = pd3dDevice->CreateSamplerState( &sampDesc, &g_pSamplerLinear );
	if( FAILED( hr ) )
		return hr;

	const float aspectRatio = pBackBufferSurfaceDesc->Width / (float)pBackBufferSurfaceDesc->Height;
	g_projection = XMMatrixPerspectiveFovRH(ToRadians(60.f), aspectRatio, 0.1f, 10000.0f);

	if (FAILED(Render_Init(pd3dDevice, numWorldIndices)))
	{
		return hr;
	}

	LoadShaders("data/baseq3/");

	std::vector<std::string> texturePaths;
	Q3BSP_GetTexturePaths(g_bsp, texturePaths);
	for (size_t i = 0; i < texturePaths.size(); i++)
	{
		Render_CreateShaderFromTexturePath(texturePaths[i]);
	}

	std::vector<unsigned char*> lightmapImages;
	int size = 0;
	Q3BSP_GetLightmapData(g_bsp, lightmapImages, &size);
	for (size_t i = 0; i < lightmapImages.size(); i++)
	{
		Render_CreateLightmapImage(lightmapImages[i], size);

		{
			char path[256];
			sprintf(path, "lightmap%d.png", i);
			FILE* f = fopen(path, "wb");
			fwrite(lightmapImages[i], size, 1, f);
			fclose(f);
		}
	}

    return S_OK;
}


//--------------------------------------------------------------------------------------
// Create any D3D11 resources that depend on the back buffer
//--------------------------------------------------------------------------------------
HRESULT CALLBACK OnD3D11ResizedSwapChain(ID3D11Device* pd3dDevice, IDXGISwapChain* pSwapChain,
                                          const DXGI_SURFACE_DESC* pBackBufferSurfaceDesc, void* pUserContext)
{
	const float aspectRatio = pBackBufferSurfaceDesc->Width / (float)pBackBufferSurfaceDesc->Height;
	g_projection = XMMatrixPerspectiveFovRH(ToRadians(60.f), aspectRatio, 0.1f, 10000.0f);

    return S_OK;
}


//--------------------------------------------------------------------------------------
// Handle updates to the scene.  This is called regardless of which D3D API is used
//--------------------------------------------------------------------------------------
void CALLBACK OnFrameMove(double fTime, float fElapsedTime, void* pUserContext)
{
	static XMVECTOR s_forward, s_camPos, s_right, forwardCrossUp;
	const float rotationSpeed = 90.0f;

	g_camXDegrees += (rotationSpeed * fElapsedTime) * g_camXRot;
	g_camYDegrees += (rotationSpeed * fElapsedTime) * g_camYRot;
	if (g_camXDegrees > 90.f)
	{
		g_camXDegrees = 90.f;
	}
	if (g_camXDegrees < -90.f)
	{
		g_camXDegrees = -90.f;
	}

	const float moveSpeed = 300.0f;
	const float deltaForward = fElapsedTime * moveSpeed * g_camSpeed;
	const float deltaRight = fElapsedTime * moveSpeed * g_camStrafeSpeed;
	const float deltaUp = fElapsedTime * moveSpeed * g_camVerticalSpeed;

	XMVECTOR q = XMQuaternionRotationRollPitchYaw(ToRadians(g_camXDegrees), ToRadians(g_camYDegrees), 0.f);
	XMVECTOR forward = XMVectorSet(0.f, 0.f, -1.f, 0.f);
	XMVECTOR worldForward = XMVector3Rotate(forward, q);
	XMVECTOR right = XMVectorSet(1.f, 0.f, 0.f, 0.f);
	XMVECTOR worldRight = XMVector3Rotate(right, q);
	XMVECTOR up = XMVectorSet(0.f, 1.f, 0.f, 0.f);
	XMVECTOR worldUp = XMVector3Rotate(up, q);

	XMVECTOR deltaWorldForward = XMVectorScale(worldForward, deltaForward);
	XMVECTOR deltaWorldRight = XMVectorScale(worldRight, deltaRight);
	XMVECTOR deltaWorldUp = XMVectorScale(worldUp, deltaUp);

	XMVECTOR deltaWorldPos = XMVectorSet(0.f, 0.f, 0.f, 0.f);
	deltaWorldPos = XMVectorAdd(deltaWorldPos, deltaWorldForward);
	deltaWorldPos = XMVectorAdd(deltaWorldPos, deltaWorldRight);
	deltaWorldPos = XMVectorAdd(deltaWorldPos, deltaWorldUp);

	g_camPos = XMVectorAdd(g_camPos, deltaWorldPos);

	if (g_camSpeed != 0.f)
	{
	//	Log_Printf("camXDegrees=%f\n", g_camXDegrees);
	//	Log_Printf("camYDegrees=%f\n", g_camYDegrees);
	//	Log_Printf("camPos=%.2f %.2f %.2f\n", XMVectorGetByIndex(g_camPos, 0),
	//		XMVectorGetByIndex(g_camPos, 1), XMVectorGetByIndex(g_camPos, 2));
	//	Log_Printf("delta=%.2f %.2f %.2f\n", XMVectorGetByIndex(deltaWorldPos, 0),
	//		XMVectorGetByIndex(deltaWorldPos, 1), XMVectorGetByIndex(deltaWorldPos, 2));
	//	Log_Printf("forward=%.2f %.2f %.2f\n", XMVectorGetByIndex(s_forward, 0),
	//		XMVectorGetByIndex(s_forward, 1), XMVectorGetByIndex(s_forward, 2));
	//	Log_Printf("right=%.2f %.2f %.2f\n", XMVectorGetByIndex(s_right, 0),
	//		XMVectorGetByIndex(s_right, 1), XMVectorGetByIndex(s_right, 2));

		{
	//		Log_Printf("forward x up = %.2f %.2f %.2f\n", XMVectorGetByIndex(forwardCrossUp, 0),
	//			XMVectorGetByIndex(forwardCrossUp, 1), XMVectorGetByIndex(forwardCrossUp, 2));
		}
	}
}


//--------------------------------------------------------------------------------------
// Render the scene using the D3D11 device
//--------------------------------------------------------------------------------------
void CALLBACK OnD3D11FrameRender(ID3D11Device* pd3dDevice, ID3D11DeviceContext* pd3dImmediateContext,
                                  double fTime, float fElapsedTime, void* pUserContext)
{
	HRESULT hr;

	static int s_frames;
	static float s_frameTime;

	s_frames++;
	s_frameTime += fElapsedTime;
	if (s_frameTime >= 10.f)
	{
		Log_Printf("FPS %f\n", (float)s_frames / 10.f);

		s_frames = 0;
		s_frameTime = 0.f;
	}

	g_world = XMMatrixIdentity();
	g_view = XMMatrixIdentity();

	const float x = XMVectorGetByIndex(g_camPos, 0);
	const float y = XMVectorGetByIndex(g_camPos, 1);
	const float z = XMVectorGetByIndex(g_camPos, 2);
	XMMATRIX translation = XMMatrixTranslation(x, y, -z);
	g_view *= translation;
	
	XMMATRIX rotate = XMMatrixRotationY(ToRadians(g_camYDegrees));
	g_view *= rotate;
	rotate = XMMatrixRotationX(ToRadians(g_camXDegrees));
	g_view *= rotate;

	XMMATRIX worldView = g_world * g_view;

    // Clear render target and the depth stencil 
    float ClearColor[4] = { 1.0f, 0.0f, 1.0f, 0.0f };

    ID3D11RenderTargetView* pRTV = DXUTGetD3D11RenderTargetView();
    ID3D11DepthStencilView* pDSV = DXUTGetD3D11DepthStencilView();
    pd3dImmediateContext->ClearRenderTargetView(pRTV, ClearColor);
    pd3dImmediateContext->ClearDepthStencilView(pDSV, D3D11_CLEAR_DEPTH, 1.0, 0);

	{
		ConstantBuffer cb;
		cb.worldView = XMMatrixTranspose(worldView);
		cb.proj = XMMatrixTranspose(g_projection);
	
		XMVECTOR lightDir = XMVectorSet(2.f, 5.f, 1.f, 1.f);
		lightDir = XMVector3Normalize(lightDir);

		XMMATRIX rotate = XMMatrixRotationY(-2.f * (fTime));
		lightDir = XMVector3Transform(lightDir, rotate);
		XMStoreFloat4(&cb.lightDir, lightDir);
	
		cb.lightColour.x = cb.lightColour.y = cb.lightColour.z = 0.5f;
		cb.lightColour.w = 1.f;
		pd3dImmediateContext->UpdateSubresource(g_constantBuffer, 0, NULL, &cb, 0, 0);
	}

	pd3dImmediateContext->IASetInputLayout(g_inputLayout);
	
	if (g_wireframe)
	{
		pd3dImmediateContext->RSSetState(g_rasterizeStateWireframe);
	}
	else
	{
		pd3dImmediateContext->RSSetState(g_rasterizeStateSolid);
	}

	pd3dImmediateContext->VSSetShader(g_vertexShader, NULL, 0);
	pd3dImmediateContext->VSSetConstantBuffers(0, 1, &g_constantBuffer);

	UINT stride = sizeof(SimpleVertex);
	UINT offset = 0;
	pd3dImmediateContext->IASetVertexBuffers(0, 1, &g_vertexBuffer, &stride, &offset);
	pd3dImmediateContext->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST);

	pd3dImmediateContext->PSSetConstantBuffers(0, 1, &g_constantBuffer);
	pd3dImmediateContext->PSSetSamplers(0, 1, &g_pSamplerLinear);


	Render_BeginFrame();

	const float px = XMVectorGetByIndex(g_camPos, 0);
	const float py = XMVectorGetByIndex(g_camPos, 1);
	const float pz = XMVectorGetByIndex(g_camPos, 2);
	float pos[3] = { px, py, pz };
	Q3BSP_GetWorldSurfacesForPosition(g_bsp, pos);

	Render_DrawFrame();
}

#if 0

//	Render_Begin();

//	const float px = XMVectorGetByIndex(g_camPos, 0);
//	const float py = XMVectorGetByIndex(g_camPos, 1);
//	const float pz = XMVectorGetByIndex(g_camPos, 2);
//	float pos[3] = { px, py, pz };
//	Q3BSP_GetWorldSurfacesForPosition(g_bsp, pos);
//	Log_Printf("worldSurfaces=%d\n", worldSurfaces.size());

	// This currently breaks the renderer, somehow
//	Render_SortSurface();

	const int maxIndexes = MAX_INDEXES * 16;
	int texture = TEXTURE_INVALID;
	int lightmap = LIGHTMAP_INVALID;
	int type = Surface_Invalid;
	int currentIndex = 0;
//	for (size_t i = 0; i < worldSurfaces.size(); i++)

	WorldSurface* worldSurfaces = Render_GetFirstSurface();
	const int numWorldSurfaces = Render_GetNumSurfaces();

//	Log_Printf("worldSurfaces=%d\n", numWorldSurfaces);

#if 1

//	for (int lm = -1; lm < (int)g_lightmapResources.size(); lm++)
//	for (size_t lmIndex = 0; lmIndex < lightmaps.size(); lmIndex++)
	{
//		int lm = lightmaps[lmIndex];
//		if (lm == -1)
		{
//			pd3dImmediateContext->PSSetShader(g_diffuseShader, NULL, 0);
		}
//		else
		{
//			pd3dImmediateContext->PSSetShader(g_diffuseLightmapShader, NULL, 0);
//			pd3dImmediateContext->PSSetShaderResources(1, 1, &g_lightmapResources.at(lm));
		}

		D3D11_BUFFER_DESC bd;
		D3D11_SUBRESOURCE_DATA initData;

//		for (size_t i = 0; i < g_worldResources.size(); i++)
		{
			int currentIndex = 0;
	
//			ID3D11ShaderResourceView* texture = g_worldResources.at(i);
//			if (texture == NULL)
			{
//				texture = g_defaultTextureResource;
			}
	
			for (size_t j = 0; j < numWorldSurfaces; j++)
			{
				WorldSurface* s = worldSurfaces + j;
	
//				if (s->texture != i || s->lightmap != lm)
//				{
//					continue;
//				}
	
//				if (s->type == Surface_TriangleList)
//				{
//					if (currentIndex + s->numIndexes <= maxIndexes)
//					{
//						memcpy(&g_surfaceIndexes[currentIndex], s->indexes, sizeof(int) * s->numIndexes);
//						currentIndex += s->numIndexes;
//					}
//				}

				if (s->type == Surface_TriangleList)
				{
					pd3dImmediateContext->DrawIndexed(s->numIndexes, s->firstPatchIndex, s->firstPatchVertex);
				}
			}
	
//			if (currentIndex == 0)
//			{
//				continue;
//			}
	
//			pd3dImmediateContext->PSSetShaderResources(0, 1, &texture);

/*
			ZeroMemory(&bd, sizeof(bd));
			bd.Usage = D3D11_USAGE_DEFAULT;
			bd.BindFlags = D3D11_BIND_INDEX_BUFFER;
			bd.CPUAccessFlags = 0;
			ZeroMemory(&initData, sizeof(initData));
			bd.ByteWidth = sizeof(int) * (currentIndex);
			initData.pSysMem = &g_surfaceIndexes[0];
			hr = pd3dDevice->CreateBuffer(&bd, &initData, &g_indexBuffer);
			if(FAILED(hr))
			{
				return;
			}

			UINT stride = sizeof(SimpleVertex);
			UINT offset = 0;
			pd3dImmediateContext->IASetVertexBuffers(0, 1, &g_vertexBuffer, &stride, &offset);
			pd3dImmediateContext->IASetIndexBuffer(g_indexBuffer, DXGI_FORMAT_R32_UINT, 0);
			pd3dImmediateContext->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST);
			pd3dImmediateContext->DrawIndexed(currentIndex, 0, 0);

			SAFE_RELEASE(g_indexBuffer);
*/
			pd3dImmediateContext->IASetVertexBuffers(0, 1, &g_patchVertexBuffer, &stride, &offset);
			pd3dImmediateContext->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST);
			pd3dImmediateContext->IASetIndexBuffer(g_patchIndexBuffer, DXGI_FORMAT_R32_UINT, 0);
			for (int j = 0; j < numWorldSurfaces; j++)
			{
				WorldSurface* s = worldSurfaces + j;

//				if (s->texture != i || s->lightmap != lm)
//				{
//					continue;
//				}

				if (s->type == Surface_Patch)
				{
					UINT stride = sizeof(SimpleVertex);
					UINT offset = 0;

					for (int x = 0; x < s->numPatchIndexes; x++)
					{
						pd3dImmediateContext->DrawIndexed(s->numIndexes, s->firstPatchIndex, 0);
					}

				}
			}

		}
	}
#endif
}

#endif

//--------------------------------------------------------------------------------------
// Release D3D11 resources created in OnD3D11ResizedSwapChain 
//--------------------------------------------------------------------------------------
void CALLBACK OnD3D11ReleasingSwapChain(void* pUserContext)
{
}


//--------------------------------------------------------------------------------------
// Release D3D11 resources created in OnD3D11CreateDevice 
//--------------------------------------------------------------------------------------
void CALLBACK OnD3D11DestroyDevice(void* pUserContext)
{
	Render_Shutdown();

	SAFE_RELEASE(g_vertexShader);
	SAFE_RELEASE(g_inputLayout);
	SAFE_RELEASE(g_vertexBuffer);
//	SAFE_RELEASE(g_indexBuffer);
	SAFE_RELEASE(g_diffuseShader);
	SAFE_RELEASE(g_constantBuffer);
	SAFE_RELEASE(g_rasterizeStateSolid);
	SAFE_RELEASE(g_pSamplerLinear);
	SAFE_RELEASE(g_rasterizeStateWireframe);
}


//--------------------------------------------------------------------------------------
// Handle messages to the application
//--------------------------------------------------------------------------------------
LRESULT CALLBACK MsgProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam,
                          bool* pbNoFurtherProcessing, void* pUserContext)
{
//	g_camera.HandleMessages(hWnd, uMsg, wParam, lParam);
    return 0;
}

//--------------------------------------------------------------------------------------
// Handle key presses
//--------------------------------------------------------------------------------------
void CALLBACK OnKeyboard(UINT nChar, bool bKeyDown, bool bAltDown, void* pUserContext)
{
	switch (nChar)
	{
	case 'W':
	case 'w':
		if (bKeyDown)
		{
			g_camSpeed = 1.0f;
		}
		else
		{
			g_camSpeed = 0.0f;
		}
		break;

	case 'S':
	case 's':
		if (bKeyDown)
		{
			g_camSpeed = -1.0f;
		}
		else
		{
			g_camSpeed = 0.0f;
		}
		break;

	case 'A':
	case 'a':
		if (bKeyDown)
		{
			g_camStrafeSpeed = 1.0f;
		}
		else
		{
			g_camStrafeSpeed = 0.0f;
		}
		break;

	case 'D':
	case 'd':
		if (bKeyDown)
		{
			g_camStrafeSpeed = -1.0f;
		}
		else
		{
			g_camStrafeSpeed = 0.0f;
		}
		break;

	case VK_LEFT:
		if (bKeyDown)
		{
			g_camYRot = -1.0f;
		}
		else
		{
			g_camYRot = 0.0f;
		}
		break;

	case VK_RIGHT:
		if (bKeyDown)
		{
			g_camYRot = 1.0f;
		}
		else
		{
			g_camYRot = 0.f;
		}
		break;

	case VK_UP:
		if (bKeyDown)
		{
			g_camXRot = -1.0f;
		}
		else
		{
			g_camXRot = 0.0f;
		}
		break;

	case VK_DOWN:
		if (bKeyDown)
		{
			g_camXRot = 1.0f;
		}
		else
		{
			g_camXRot = 0.f;
		}
		break;
	case VK_F1:
		if (bKeyDown)
		{
			g_wireframe = !g_wireframe;
			Log_Printf("Wireframe=%s\n", g_wireframe ? "true" : "false");
		}

		break;
	}
}


//--------------------------------------------------------------------------------------
// Handle mouse button presses
//--------------------------------------------------------------------------------------
void CALLBACK OnMouse(bool bLeftButtonDown, bool bRightButtonDown, bool bMiddleButtonDown,
                       bool bSideButton1Down, bool bSideButton2Down, int nMouseWheelDelta,
                       int xPos, int yPos, void* pUserContext)
{
}


//--------------------------------------------------------------------------------------
// Call if device was removed.  Return true to find a new device, false to quit
//--------------------------------------------------------------------------------------
bool CALLBACK OnDeviceRemoved(void* pUserContext)
{
    return true;
}


//--------------------------------------------------------------------------------------
// Initialize everything and go into a render loop
//--------------------------------------------------------------------------------------
int WINAPI wWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPWSTR lpCmdLine, int nCmdShow)
{
    // Enable run-time memory check for debug builds.
#if defined(DEBUG) | defined(_DEBUG)
    _CrtSetDbgFlag(_CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF);
#endif

	if (!Log_Init())
	{
		MessageBox(NULL, L"Could not init log", L"Error", MB_OK | MB_SYSTEMMODAL);
		return -1;
	}

//	g_bsp = Q3BSP_Load("box0.bsp");
//	g_bsp = Q3BSP_Load("test.bsp");
//	g_bsp = Q3BSP_Load("vis_test.bsp");
//	g_bsp = Q3BSP_Load("data/maps/transit.bsp");
	g_bsp = Q3BSP_Load("data/baseq3/maps/q3dm1.bsp");    
	if (g_bsp == NULL)
	{
		MessageBox(NULL, L"Unable to load BSP", L"Error", MB_OK | MB_SYSTEMMODAL);
		return -1;
	}

	float x, y, z;
	Q3BSP_GetStartPos(g_bsp, &x, &y, &z);
	g_camPos = XMVectorSet(-x, -y, z, 0.f);
	Log_Printf("startPos=%.2f %.2f %.2f\n", x, y, z);

    // DXUT will create and use the best device (either D3D9 or D3D11) 
    // that is available on the system depending on which D3D callbacks are set below

    // Set general DXUT callbacks
    DXUTSetCallbackFrameMove(OnFrameMove);
    DXUTSetCallbackKeyboard(OnKeyboard);
    DXUTSetCallbackMouse(OnMouse);
    DXUTSetCallbackMsgProc(MsgProc);
    DXUTSetCallbackDeviceChanging(ModifyDeviceSettings);
    DXUTSetCallbackDeviceRemoved(OnDeviceRemoved);

    // Set the D3D9 DXUT callbacks. Remove these sets if the app doesn't need to support D3D9
    DXUTSetCallbackD3D9DeviceAcceptable(IsD3D9DeviceAcceptable);
    DXUTSetCallbackD3D9DeviceCreated(OnD3D9CreateDevice);
    DXUTSetCallbackD3D9DeviceReset(OnD3D9ResetDevice);
    DXUTSetCallbackD3D9FrameRender(OnD3D9FrameRender);
    DXUTSetCallbackD3D9DeviceLost(OnD3D9LostDevice);
    DXUTSetCallbackD3D9DeviceDestroyed(OnD3D9DestroyDevice);

    // Set the D3D11 DXUT callbacks. Remove these sets if the app doesn't need to support D3D11
    DXUTSetCallbackD3D11DeviceAcceptable(IsD3D11DeviceAcceptable);
    DXUTSetCallbackD3D11DeviceCreated(OnD3D11CreateDevice);
    DXUTSetCallbackD3D11SwapChainResized(OnD3D11ResizedSwapChain);
    DXUTSetCallbackD3D11FrameRender(OnD3D11FrameRender);
    DXUTSetCallbackD3D11SwapChainReleasing(OnD3D11ReleasingSwapChain);
    DXUTSetCallbackD3D11DeviceDestroyed(OnD3D11DestroyDevice);

    // Perform any application-level initialization here

    DXUTInit(true, true, NULL); // Parse the command line, show msgboxes on error, no extra command line params
    DXUTSetCursorSettings(true, true); // Show the cursor and clip it when in full screen
    DXUTCreateWindow(L"EmptyProject11");

    // Only require 10-level hardware
    DXUTCreateDevice(D3D_FEATURE_LEVEL_10_0, true, 800, 600);
    DXUTMainLoop(); // Enter into the DXUT ren  der loop

    // Perform any application-level cleanup here
	Q3BSP_Release(&g_bsp);

	Log_Shutdown();

    return DXUTGetExitCode();
}


